/*
 * Copyright (c) 2008-2011, Petr Panteleyev
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *    3. The name of the author may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.panteleyev.html;

import java.awt.Color;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Enumeration;

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLDocument;

import org.panteleyev.swing.ClipboardUtil;

public class HTMLEditor extends Editor {
    private MutableHTMLDocument doc;
    private CustomHTMLEditorKit kit;

    public HTMLEditor() {
        kit = new CustomHTMLEditorKit();
    }

    @Override
    public MutableHTMLDocument getDocument() {
        return doc;
    }

    @Override
    public CustomHTMLEditorKit getKit() {
        return kit;
    }

    @Override
    public void newDocument() {
        doc = (MutableHTMLDocument)kit.createDocument();
        getEditorPane().setDocument(doc);
    }

    @Override
    public String getHtml() {
        if (getEditorPane() == null) {
            return null;
        }

        try {
            StringWriter sw = new StringWriter();
            getKit().write(sw, getDocument(), 0, getDocument().getLength());
            sw.close();
            return sw.toString();
        } catch (IOException | BadLocationException ex) {
            return null;
        }
    }

    private void setAttributeSet(MutableAttributeSet attr, boolean setParagraphAttributes) {
        int begin = getEditorPane().getSelectionStart();
        int end = getEditorPane().getSelectionEnd();

        /*
         if (!getEditorPane().hasFocus()) {
            begin = m_start;
            end = m_finish;
         }
         */

        if (setParagraphAttributes) {
            getDocument().setParagraphAttributes(begin, end - begin, attr, false);
        } else {
            if (begin != end) {
                getDocument().setCharacterAttributes(begin, end - begin, attr, false);
            } else {
                MutableAttributeSet inputAttributes = getKit().getInputAttributes();
                inputAttributes.addAttributes(attr);
            }
        }

    }

    private void insertAttribute(SimpleAttributeSet attrs, Object key, Object value) {
        if (value instanceof AttributeSet) {
            AttributeSet subSet = (AttributeSet)value;
            Enumeration<?> attribEntriesSub = subSet.getAttributeNames();
            while(attribEntriesSub.hasMoreElements()) {
                Object subKey   = attribEntriesSub.nextElement();
                Object subValue = subSet.getAttribute(subKey);
                insertAttr(attrs, subKey, subValue);
            }
        } else {
            insertAttr(attrs, key, value);
        }

        if (key.toString().toLowerCase().equals("font-family")) {
            if (attrs.isDefined("face")) {
                insertAttr(attrs, "face", attrs.getAttribute("face"));
                insertAttr(attrs, "font-family", attrs.getAttribute("face"));
            } else {
                insertAttr(attrs, "face", value);
            }
        }
    }

    private void insertAttr(SimpleAttributeSet attrs, Object key, Object value) {
        while (attrs.isDefined(key)) {
            attrs.removeAttribute(key);
        }
        attrs.addAttribute(key, value);
    }

    @Override
    public void setBold() {
        MutableAttributeSet attr = new SimpleAttributeSet();
        StyleConstants.setBold(attr, true);
        setAttributeSet(attr, false);
    }

    @Override
    public void setItalic() {
        MutableAttributeSet attr = new SimpleAttributeSet();
        StyleConstants.setItalic(attr, true);
        setAttributeSet(attr, false);
    }


    @Override
    public void setUnderline() {
        MutableAttributeSet attr = new SimpleAttributeSet();
        StyleConstants.setUnderline(attr, true);
        setAttributeSet(attr, false);
    }

    @Override
    public void setStrikeThrough() {
        MutableAttributeSet attr = new SimpleAttributeSet();
        StyleConstants.setStrikeThrough(attr, true);
        setAttributeSet(attr, false);
    }

    @Override
    public void onInsertLink(HTMLLinkDialog dial) {
        int off = getEditorPane().getSelectionStart();

        String selText = getEditorPane().getSelectedText();
        int selTextLength = (selText == null)? 0 : selText.length();

        String currAnchor = "";

        if (selText != null) {
            SimpleAttributeSet sasText = null;
            for (int i = off; i < off + selTextLength; i++) {
                getEditorPane().select(i, i + 1);

                sasText = new SimpleAttributeSet(getEditorPane().getCharacterAttributes());
                Enumeration<?> attribEntries1 = sasText.getAttributeNames();

                while (attribEntries1.hasMoreElements() && currAnchor.equals("")) {
                    Object entryKey   = attribEntries1.nextElement();
                    Object entryValue = sasText.getAttribute(entryKey);

                    if(entryKey.toString().equals(HTML.Tag.A.toString())) {
                        if(entryValue instanceof SimpleAttributeSet) {
                            Enumeration<?> subAttributes = ((SimpleAttributeSet)entryValue).getAttributeNames();
                            while(subAttributes.hasMoreElements() && currAnchor.equals("")) {
                                Object subKey = subAttributes.nextElement();
                                if (subKey.toString().toLowerCase().equals("href")) {
                                    currAnchor = ((SimpleAttributeSet)entryValue).getAttribute(subKey).toString();
                                    break;
                                }
                            }
                        }
                    }
                }

                if(!currAnchor.equals("")) {
                    break;
                }
            }
        }

        if (currAnchor.length() == 0) {
            String clipText = ClipboardUtil.getAsUrl();
            if (clipText != null) {
                currAnchor = clipText;
            }
        }

        getEditorPane().select(off, off + selTextLength);
        SimpleAttributeSet sasTag  = new SimpleAttributeSet();
        SimpleAttributeSet sasAttr = new SimpleAttributeSet();

        dial.setInnerHTML(selText);
        dial.setUrl(currAnchor);

        if (dial.showDialog()) {
            String text = dial.getInnerHTML();
            if (text.length() == 0) {
                text = dial.getUrl();
            }

            getEditorPane().replaceSelection(text);
            getEditorPane().select(off, off + text.length());

            insertAttr(sasAttr, HTML.Attribute.HREF, dial.getUrl());

            if (dial.isTargetBlank()) {
                insertAttr(sasAttr, HTML.Attribute.TARGET, "_blank");
            }

            String linkTitle = dial.getLinkTitle();
            if (linkTitle.length() != 0) {
                insertAttr(sasAttr, HTML.Attribute.TITLE, linkTitle);
            }

            SimpleAttributeSet baseAttrs = new SimpleAttributeSet(getEditorPane().getCharacterAttributes());
            Enumeration<?> attribEntriesOriginal = baseAttrs.getAttributeNames();
            while (attribEntriesOriginal.hasMoreElements()) {
                Object entryKey   = attribEntriesOriginal.nextElement();
                Object entryValue = baseAttrs.getAttribute(entryKey);
                insertAttribute(sasAttr, entryKey, entryValue);
            }

            sasTag.addAttribute(HTML.Tag.A, sasAttr);

            if (dial.isBold()) {
                StyleConstants.setBold(sasTag, true);
            }

            if (dial.isItalic()) {
                StyleConstants.setItalic(sasTag, true);
            }

            getEditorPane().setCharacterAttributes(sasTag, false);

            documentChanged();

            getEditorPane().setCaretPosition(off + text.length());
            getEditorPane().select(off, off + text.length());
            getEditorPane().requestFocus();
        }
    }

    @Override
    public void insertHTML(String html, HTML.Tag startTag) {
        int pos = getEditorPane().getSelectionStart();
        getEditorPane().replaceSelection("");
        try {
            getKit().insertHTML(getDocument(), pos, html, 0, 0, startTag);
        } catch (BadLocationException | IOException ex) {
        }
    }

    private void documentChanged() {
        getEditorPane().setDocument(new HTMLDocument());
        getEditorPane().setDocument(getDocument());
        getEditorPane().revalidate();
        getEditorPane().repaint();
    }

    @Override
    public void onInsertImage(HTMLImageDialog dial) {
        String selText = getEditorPane().getSelectedText();

        dial.setAltText(selText);

        if (dial.showDialog()) {
            try {
                int pos = getEditorPane().getSelectionStart();
                HTML.Tag tag = (dial.isLinkToOriginal())? HTML.Tag.A : HTML.Tag.IMG;
                insertHTML(dial.getReference(), tag);
                getEditorPane().setCaretPosition(pos + 1);
                documentChanged();
            } catch (Exception ex) {
            }
        }
    }

    @Override
    public void setForegroundColor(Color color) {
        MutableAttributeSet attr = new SimpleAttributeSet();
        StyleConstants.setForeground(attr, color);
        setAttributeSet(attr, false);
    }

    @Override
    public void encloseText(char open, char close) {
        int sPos = getEditorPane().getSelectionStart();
        int ePos = getEditorPane().getSelectionEnd();

        try {
            getDocument().insertString(ePos, new StringBuilder().append(close).toString(), getEditorPane().getInputAttributes());
            getDocument().insertString(sPos, new StringBuilder().append(open).toString(), getEditorPane().getInputAttributes());
            getEditorPane().select(sPos, ePos + 2);
        } catch (Exception ex) {
            // ignore
        }

        // do formatting
    }

    @Override
    public void insertUnicodeCharacter(char c) {
        int sPos = getEditorPane().getSelectionStart();
        getEditorPane().replaceSelection("");
        try {
            StringBuilder b = new StringBuilder();
            b.append(c);
            getDocument().insertString(sPos, b.toString(), getEditorPane().getInputAttributes());
            getEditorPane().requestFocus();
        } catch (Exception ex) {
            // ignore
        }
    }

    @Override
    public void setFormatTag(Tag tag) {
        int len = getEditorPane().getSelectedText().length();
        SimpleAttributeSet attr = new SimpleAttributeSet(getEditorPane().getCharacterAttributes());
        attr.addAttribute(tag, new SimpleAttributeSet());
        int pos = getEditorPane().getSelectionStart();
        getEditorPane().select(pos, pos + len);
        getEditorPane().setCharacterAttributes(attr, false);
        documentChanged();
        getEditorPane().select(pos, pos + len);
    }

    public static char numberToChar(String entity) {
        if (entity.length() < 2) {
            return 0;
        }

        try {
            if (entity.charAt(0) == '#') {
                final char secondChar = entity.charAt(1);
                if ((secondChar == 'x') || (secondChar == 'X')) {
                    if ( entity.length() < 3 ) {
                        return 0;
                    } else {
                        return (char)Integer.parseInt(entity.substring(2), 16);
                    }
                }  else {
                    return (char) Integer.parseInt( entity.substring( 1 ) );
                }
            } else {
                return 0;
            }
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    public static String convertHTMLString(String html) {
        if (html == null) {
            return null;
        }

        if (html.indexOf('&') < 0) {
            return html;
        }

        int len = html.length();
        StringBuilder result = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            int pos = html.indexOf('&', i);
            if (pos < 0) {
                result.append(html.substring(i));
                break;
            } else {
                result.append(html.substring(i, pos));
                i = pos;
                int semiPos = html.indexOf(";", i);

                if (semiPos < 0) {
                    result.append('&');
                } else {
                    char ch = numberToChar(html.substring(pos + 1, semiPos));
                    if ((ch != 0) && Character.isLetter(ch)) {
                        result.append(ch);
                        i = semiPos;
                    } else {
                        result.append('&');
                    }
                }
            }
        }

        return result.toString();
    }

    @Override
    public String getTextForPost() {
        String text = getHtml();
        if (text != null) {
            text = text.replaceAll("\n", "").replaceAll("\r", "");
            int start = text.indexOf("<body>");
            if (start != -1) {
                int end = text.indexOf("</body>");
                if (end != -1) {
                    String content = text.substring(start + 6, end);
                    text = convertHTMLString(content.replaceAll("    ", "").trim());
                }
            }
        }

        return text;
    }

    @Override
    public String getTextForPreview(boolean convertLineBreaks) {
        String text = getHtml();
        if (text != null) {
            text = text.replaceAll("\n", "").replaceAll("\r", "");
            int start = text.indexOf("<body>");
            if (start != -1) {
                int end = text.indexOf("</body>");
                if (end != -1) {
                    String content = text.substring(start + 6, end);
                    if (convertLineBreaks) {
                        content = content.replaceAll("<br>", "\n");
                    }
                    text = convertHTMLString(content.replaceAll("    ", "").trim());
                }
            }
        }

        return text;
    }

    @Override
    public void fontUpdated() {
    }


}
